refactor(core): one shared record-source ladder, five plugins delegate (objectui#7632) - #7660
Conversation
…e (objectui#7632) `getDataConfig` — the ruled three-rung record-source ladder (`data`, then `staticData`, then `objectName`) — was hand-copied into five plugin components with no gate holding them together. That ladder is published contract on both faces: `packages/types/src/objectql.ts` and its zod mirror ship `.describe()` strings naming the order (objectui#6939, maintainer ruling 2026-09-02), pinned by `objectql-record-source-refinement-6939.test.ts`. A change to the ruled order had five edit sites and nothing that noticed a missed one — the AGENTS.md #0.1 drift class. `@object-ui/core` now publishes `resolveRecordSourceConfig`, beside the objectui#7627 reader `resolveRecordSourceObjectName` whose input it produces. ObjectCalendar, ObjectGantt and ObjectTree call it directly; ObjectGrid and ObjectMap keep their bare-array `data` shorthand as a documented head above it. Two divergences were measured rather than assumed, and both are preserved: - ObjectCalendar's `'data' in schema` guards are a TypeScript narrowing device for its `ObjectGridSchema | CalendarSchema` parameter, not a behavioural one — an absent property reads `undefined`, falsy either way. Pinned on a fixture that really lacks both keys. - ObjectGrid and ObjectMap normalize a bare-array `data`; calendar, gantt and tree return it verbatim. That is a real divergence on off-contract input, so the shared rung stays contract-strict and the head stays at those two sites, exactly as objectui#7627 left its off-contract tails at theirs. Both sides of the fork are pinned. `record-source-config.behaviourNeutrality-7632.test.ts` transcribes all five pre-collapse bodies verbatim and asserts the post-collapse spelling agrees with each across the whole input matrix, with lit controls proving the matrix reaches every rung and that the calendar fixtures really lack the keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
…red-get-data-config
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
契约复核 — 裁决(逐字采纳)归属:本 PR 由 档位:本席服务档位未达 transcript 核验(采信前置):子代理 transcript 中 harness 逐消息盖章的 独立性:子代理只喂了卡片 #7632、既有裁定与 PR 本身;⛔ 未喂派发简报,⛔ 未喂本席自己的结论;简报按对抗性写法给出(「你的任务是对抗性的:找出 REFUSE 的理由」)。 一处已知渲染损耗:下文「Instrument proof」段有一个尖括号片段 Contract review —
|
Fixes #7632
getDataConfig— the ruled three-rung record-source ladder — was hand-copied into fiveplugin components with no gate holding them together.
@object-ui/corenow publishes oneimplementation,
resolveRecordSourceConfig, beside the objectui#7627 readerresolveRecordSourceObjectNamewhose input it produces.Premise re-measurement (the first deliverable)
Re-measured on
origin/main1ec291c0, which carries PR #7637. The population is stillfive — #7637 collapsed the reader (which object a block resolves), not the producer.
Only the line numbers drifted:
11edab88)1ec291c0)packages/plugin-calendar/src/ObjectCalendar.tsxpackages/plugin-gantt/src/ObjectGantt.tsxpackages/plugin-grid/src/ObjectGrid.tsxpackages/plugin-map/src/ObjectMap.tsxpackages/plugin-tree/src/ObjectTree.tsxOne card claim did not survive re-measurement. The card says four of the five are
"byte-identical modulo the parameter type", with calendar the sole divergence. Measured,
there are three shapes, and the third one is behavioural:
ObjectGantt,ObjectTree— the bare ladder.ObjectCalendar— thein-guarded ladder (the card's named divergence).ObjectGrid,ObjectMap— a bare-arraydatashorthand normalized to{ provider: 'value', items }that the other three do not have.That third shape matters, because the card's "all five copies agree today for every input
tested" is false for a bare array under
data: grid and map normalize it, while calendar,gantt and tree return the array verbatim, so
dataConfig.providerreadsundefineddownstream and the block draws nothing.
Disposition: consolidate, preserving both measured divergences
The shared rung is contract-strict. Neither divergence was flattened, following the
precedent PR #7637 set when it left the off-contract
{ provider: 'object' }tails atObjectGridandObjectTreerather than folding them into the shared reader (AGENTS.md#0.1).
Calendar's
inguards — measured as type-level, not behavioural. They exist becausethe parameter is the union
ObjectGridSchema | CalendarSchemaandCalendarSchemadeclares neither
datanorstaticData. When a property is absent the read yieldsundefined, which is falsy either way, so the guard can never change which rung is taken.The shared reader's optional-property parameter accepts that union directly, so the guard
is gone rather than flattened — and the equivalence is pinned on a fixture that really
lacks both keys, not argued in prose.
The array shorthand — measured as real, and kept at the two sites that have it.
ObjectGridandObjectMapkeep the head above the shared call. Hoisting it above thecall is behaviour-neutral because an array is always truthy,
[]included, soif (schema.data)could never have let one fall through to rung 2 or 3.ObjectTree'sschema: anynow goes through the shared reader's typed parameter. Typesare erased at runtime, so nothing it resolves moves; no call-site behaviour changed, so
there is nothing to report under that heading.
A docblock claim corrected
ObjectMap.tsx's docblock listedObjectTreeamong the blocks that accept the arrayshorthand. Measured:
plugin-treehas noArray.isArrayonschema.dataanywhere (litcontrol — the same grep finds its five other
Array.isArrayuses), so it answers theshorthand with a silently empty tree. The comment now says so. That divergence is not
fixed here; it is reported separately for triage.
Evidence
Behaviour neutrality is the claim, so
packages/core/src/utils/__tests__/record-source-config.behaviourNeutrality-7632.test.tsfollows the pattern #7637 shipped: it transcribes all five pre-collapse bodies verbatim
and asserts the post-collapse spelling agrees with each across a 15-shape contract-valid
matrix, plus a 4-shape off-contract fork that pins both sides of the array-shorthand
divergence. Two lit controls guard the instrument: one asserts the matrix actually reaches
every rung of the ladder, one asserts the calendar fixtures really lack the keys.
Ablation legs, each with the prediction written before the run, the mutation proved on disk
by anchored grep counts and
git hash-objectmovement off the HEAD blob, and therestore proved by blob equality plus an empty
git diff HEADunder anEXIT INT TERMtrap:all-threeplus the gantt/tree/calendar arm ofarray-shorthand+staticData; sibling greenObjectMapObjectMap.schemaDataShorthand.test.tsxgoes redThe second leg is what makes the preservation argument measured rather than stylistic: had
it stayed green, the head would have been decorative and folding it in would have been the
right call.
Both ablations read source, not
dist— the neutrality test imports'../record-source.js'from inside core and the map test imports
'./ObjectMap'relatively, so no rebuild legapplies. That was checked rather than assumed, since a green ablation from a stale
distis indistinguishable from a dead assertion.
Verification, all at
028f4f20The merge of
origin/mainbelow is included, so these are readings on the tree as pushed.turbo run build --filter='!@object-ui/site'— 43/43 successful.type-checkfor core and all five plugins — exit 0, all 6 script names echoed (a--filtermatching zero scripts exits 0 silently, so the echo is the guard). Core'stsconfig.test.jsonis in that run and--listFilesconfirms the new test file is insideits program, so this is a measurement and not a NOT-MEASURED reading.
packages/core/+ calendar + tree + map — 168 files, 2756tests passed;
packages/plugin-grid/+packages/plugin-gantt/— 175 files, 1511 testspassed.
check:control-bytes,check:readme-exports,check:element-data-source-declaration,check:phantom-deps,check:self-import,check:esm-specifiers,check:sdui-registration-pins,check:spec-symbols,check:entry-guard,check:side-effects-array,check:node-esm-load,check:vi-mock-specifiers,check:vi-mock-inherit— all exit 0.lintfor the six affected packages — exit 0, 0 errors (warnings pre-existing; the treepackage sheds one
no-explicit-anywith theschema: anycopy gone). Type-aware lintingis not enabled in
eslint.config.js, so this diff cannot move the verdict on any file itdoes not touch.
check:readme-exportsandcheck:sdui-registration-pinsfirst came back exit 1 and exit 2with an unbuilt population — a
PRECONDITION NOT MET, which is NOT MEASURED rather than ared. Both were re-run after a full workspace build and report lit populations: readme-exports
reads 37 of 40 packages with 52 keys compared both ways, and the sdui gate weighs 518 chunks
with its 3 ruled controls in the derived set.
Clause-② determination: yes — parked
resolveRecordSourceConfigis a new exported symbol on@object-ui/core, so the publishedsurface widens and there is no shape of this change that avoids it: the reader is consumed
from five other packages, which requires the export.
needs:contract-reviewapplied to bothcarriers; opened as a draft and deliberately not flipped ready or enqueued, the same route
PR #7637 took.
Scope
packages/types/**untouched. None of the six object-schema census files (#7642) touched.No sixth producer added and
KanbanSchema.datanot re-typed — #7651 is an open maintainerdecision and stays out of scope here.
scripts/check-doc-links.mjsuntouched (#7644).PR #7648 landed while this was in flight and touches
ObjectTree.tsxandObjectCalendar.tsxin different regions;origin/mainwas merged in, no conflicts, andthe whole verification above was re-run on the merge result. That branch's history was not
rewritten.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code